Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add missing imports and modules #344

Merged
merged 41 commits into from
Jun 10, 2022

Conversation

DaelonSuzuka
Copy link
Contributor

@DaelonSuzuka DaelonSuzuka commented May 1, 2022

QtWebSockets was not being imported when using PySide6.

Some other modules appear to missing imports as well:

  • Qt3DAnimation.py
  • QtDBus.py
  • QtDesigner.py
  • QtLocation.py
  • QtNetworkAuth.py
  • QtOpenGlWidgets.py
  • QtPositioning.py
  • QtTextToSpeech.py
  • QtWebEngine.py
  • QtXmlPatterns.py

It's possible that some of these were omitted on purpose, but there's no indication of that in the code.

It would also help readability and auditability to standardize the formatting of these files. I'd be willing to do this if the project is open to it.

Additionally, PySide6 has been split into two packages, PySide6-Essentials and PySide6-Addons, and it's probably incorrect behavior to assume that all of PySide6 is present without explicitly checking.

@dalthviz dalthviz changed the title Add missing PySide6 import PR: Add missing PySide6 import May 2, 2022
@dalthviz dalthviz added this to the v2.1.1 milestone May 2, 2022
@dalthviz
Copy link
Member

dalthviz commented May 2, 2022

Hi @DaelonSuzuka thank you for checking into this and submiting an initial fix!

Regarding the missing imports I would say that was caused due to those modules not being available when the PR for adding support for PySide6 was being implemented (I think that was between when PySide6 6.1.3 was the latest release and PySide6 6.2.0 was going to be released). So, for example, if you try to import QtWebSockets with PySide6 6.1.3 you get a ModuleNotFoundError:

imagen

However if those modules are now available then we should add them 👍

Would you like to add all the missing imports on this PR @DaelonSuzuka ?

Also, it would be nice then to revisit the test skips to for those modules (to run them againts PySide6/PyQt6). For example, the current test for QtWebSockets is only running for Qt5 (PyQt5 or PySide2):

@pytest.mark.skipif(not (PYQT5 or PYSIDE2), reason="Only available in Qt5 bindings")
def test_qtwebsockets():
"""Test the qtpy.QtWebSockets namespace"""
from qtpy import QtWebSockets

Regarding the other elements (files standarizartion and checks for PySide6-Essentials and PySide6-Addon), could you create new issues to discuss them please? Thank you again!

@DaelonSuzuka
Copy link
Contributor Author

Regarding the missing imports I would say that was caused due to those modules not being available when the PR for adding support for PySide6 was being implemented (I think that was between when PySide6 6.1.3 was the latest release and PySide6 6.2.0 was going to be released). So, for example, if you try to import QtWebSockets with PySide6 6.1.3 you get a ModuleNotFoundError:

This raises an important question: What versions of Qt libraries is QtPy committed to supporting? Only the most recent version of each of the 4, or should it correctly work with any version you can get from PyPA? Some users might have a reason to stick to an older version of Qt, is that QtPy's problem or their problem?

Would you like to add all the missing imports on this PR @DaelonSuzuka ?

I can give it a shot. I'll try to have it in sometime this week.

@dalthviz
Copy link
Member

dalthviz commented May 2, 2022

This raises an important question: What versions of Qt libraries is QtPy committed to supporting? Only the most recent version of each of the 4, or should it correctly work with any version you can get from PyPA? Some users might have a reason to stick to an older version of Qt, is that QtPy's problem or their problem?

Currently for Qt5/PyQt5 the minimum is 5.9, for PySide2 is 5.12 and for Qt6 I think we came to the conclusion to enforce using Qt 6.2.0+ (PyQt6 and PySide6). The minimum versions definitions are here:

qtpy/qtpy/__init__.py

Lines 93 to 96 in 3c6bd90

# Minimum supported versions of Qt and the bindings
QT5_VERSION_MIN = PYQT5_VERSION_MIN = '5.9.0'
PYSIDE2_VERSION_MIN = '5.12.0'
QT6_VERSION_MIN = PYQT6_VERSION_MIN = PYSIDE6_VERSION_MIN = '6.2.0'

For any lower version we are currently raising a warning (basically suggesting to update to a more recent version and saying that we don't support such version). The point where that is being validated is here:

qtpy/qtpy/__init__.py

Lines 229 to 255 in 3c6bd90

def _warn_old_minor_version(name, old_version, min_version):
"""Warn if using a Qt or binding version no longer supported by QtPy."""
warning_message = (
"{name} version {old_version} is not supported by QtPy. "
"To ensure your application works correctly with QtPy, "
"please upgrade to {name} {min_version} or later.".format(
name=name, old_version=old_version, min_version=min_version))
warnings.warn(warning_message, PythonQtWarning)
# Warn if using an End of Life or unsupported Qt API/binding minor version
if QT_VERSION:
if QT5 and (parse(QT_VERSION) < parse(QT5_VERSION_MIN)):
_warn_old_minor_version('Qt5', QT_VERSION, QT5_VERSION_MIN)
elif QT6 and (parse(QT_VERSION) < parse(QT6_VERSION_MIN)):
_warn_old_minor_version('Qt6', QT_VERSION, QT6_VERSION_MIN)
if PYQT_VERSION:
if PYQT5 and (parse(PYQT_VERSION) < parse(PYQT5_VERSION_MIN)):
_warn_old_minor_version('PyQt5', PYQT_VERSION, PYQT5_VERSION_MIN)
elif PYQT6 and (parse(PYQT_VERSION) < parse(PYQT6_VERSION_MIN)):
_warn_old_minor_version('PyQt6', PYQT_VERSION, PYQT6_VERSION_MIN)
elif PYSIDE_VERSION:
if PYSIDE2 and (parse(PYSIDE_VERSION) < parse(PYSIDE2_VERSION_MIN)):
_warn_old_minor_version('PySide2', PYSIDE_VERSION, PYSIDE2_VERSION_MIN)
elif PYSIDE6 and (parse(PYSIDE_VERSION) < parse(PYSIDE6_VERSION_MIN)):
_warn_old_minor_version('PySide6', PYSIDE_VERSION, PYSIDE6_VERSION_MIN)

@DaelonSuzuka
Copy link
Contributor Author

Thanks for the references. This looks relatively straightforward.

@dalthviz
Copy link
Member

dalthviz commented May 2, 2022

Awesome! If you have any further questions or need help let us know 👍

@CAM-Gerlach
Copy link
Member

It would also help readability and auditability to standardize the formatting of these files.

Thanks, I'd been hoping to do the same for some time now. I've opened #345 to track this.

@CAM-Gerlach

This comment was marked as outdated.

@DaelonSuzuka

This comment was marked as outdated.

@CAM-Gerlach

This comment was marked as outdated.

@dalthviz

This comment was marked as outdated.

@DaelonSuzuka DaelonSuzuka force-pushed the patch-1 branch 2 times, most recently from ccf308a to 8781e9a Compare May 8, 2022 18:39
@DaelonSuzuka
Copy link
Contributor Author

I spent a bunch of time trying to cross-reference the module lists in each library's documentation, and then I remembered that I'm a programmer.

So, I created a venv, installed all 4 qt libs, scraped them for files named Qt*.pyi, and shoved all that into a dictionary of module name: [libraries it exists in] I then compared those module names with the files that exist in the qtpy/ directory.

The following table is every module in PyQt5, PyQt6, PySide2, and PySide6, along with which libraries each module is present in. Modules that are currently missing from qtpy are marked with a star.

Modules
Qt3DAnimation        ['PySide2', 'PySide6']
Qt3DCore             ['PySide2', 'PySide6']
Qt3DExtras           ['PySide2', 'PySide6']
Qt3DInput            ['PySide2', 'PySide6']
Qt3DLogic            ['PySide2', 'PySide6']
Qt3DRender           ['PySide2', 'PySide6']
QtBluetooth*         ['PyQt5', 'PyQt6', 'PySide6']
QtCharts[1]          ['PySide2', 'PySide6']
QtConcurrent*        ['PySide2', 'PySide6']
QtCore               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtDBus               ['PyQt5', 'PyQt6', 'PySide6']
QtDataVisualization  ['PySide2', 'PySide6']
QtDesigner           ['PyQt5', 'PyQt6', 'PySide6']
QtGui                ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtHelp               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtLocation           ['PyQt5', 'PySide2']
QtMultimedia         ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtMultimediaWidgets  ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtNetwork            ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtNetworkAuth        ['PySide6']
QtNfc*               ['PyQt5', 'PyQt6', 'PySide6']
QtOpenGL             ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtOpenGLFunctions*   ['PySide2']
QtOpenGLWidgets      ['PyQt6', 'PySide6']
QtPositioning        ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtPrintSupport       ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQml                ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQuick              ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQuick3D*           ['PyQt5', 'PyQt6', 'PySide6']
QtQuickControls2*    ['PySide2', 'PySide6']
QtQuickWidgets       ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtRemoteObjects      ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtScript*            ['PySide2']
QtScriptTools*       ['PySide2']
QtScxml*             ['PySide2', 'PySide6']
QtSensors            ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSerialPort         ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSql                ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtStateMachine*      ['PySide6']
QtSvg                ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSvgWidgets*        ['PyQt6', 'PySide6']
QtTest               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtTextToSpeech       ['PyQt5', 'PySide2']
QtUiTools*           ['PySide2', 'PySide6']
QtWebChannel         ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWebEngine          ['PySide2']
QtWebEngineCore      ['PySide2', 'PySide6']
QtWebEngineQuick     ['PySide6']
QtWebEngineWidgets   ['PySide2', 'PySide6']
QtWebSockets         ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWidgets            ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtX11Extras*[2]      ['PyQt5', 'PySide2']
QtXml                ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtXmlPatterns        ['PyQt5', 'PySide2']

[1] QtCharts is known to be missing from PyQt5 and PyQt6, requiring a seperate pip install
[2] This test was performed on an Ubuntu 20.04 machine, QtWinExtras and QtMacExtras would be present on Windows, and Mac, respectively

Missing Modules Only
QtBluetooth          ['PyQt5', 'PyQt6', 'PySide6']
QtConcurrent         ['PySide2', 'PySide6']
QtNfc                ['PyQt5', 'PyQt6', 'PySide6']
QtOpenGLFunctions    ['PySide2']
QtQuick3D            ['PyQt5', 'PyQt6', 'PySide6']
QtQuickControls2     ['PySide2', 'PySide6']
QtScript             ['PySide2']
QtScriptTools        ['PySide2']
QtScxml              ['PySide2', 'PySide6']
QtStateMachine       ['PySide6']
QtSvgWidgets         ['PyQt6', 'PySide6']
QtUiTools            ['PySide2', 'PySide6']
QtX11Extras          ['PyQt5', 'PySide2']

Are there any objections to my adding the missing modules?

Additionally, I don't know why the Qt3D**** modules appear to be missing from PyQt5 and PyQt6. They're listed in the docs, so I don't know why they aren't present. Any insight here would be appreciated.

@CAM-Gerlach
Copy link
Member

Wow, thanks for all the work @DaelonSuzuka ! That's awesome!

Are there any objections to my adding the missing modules?

So long as they are included in all supported versions of each binding (5.9, 5.12, 5.15 and 6.2) on each platform or have the appropriate conditionals, and are added to our tests (which should mostly verify that), I don't see why not, but maybe @dalthviz has more insight.

Additionally, I don't know why the Qt3D**** modules appear to be missing from PyQt5 and PyQt6. They're listed in the docs, so I don't know why they aren't present. Any insight here would be appreciated.

I'm guessing because they are a separate PyPI package like QtCharts?

@DaelonSuzuka
Copy link
Contributor Author

I'm guessing because they are a separate PyPI package like QtCharts?

Awesome, thanks. I searched PyPI for exactly this but didn't get any hits.

@DaelonSuzuka
Copy link
Contributor Author

Wow. Riverbank doesn't seem to have a list of all their packages, and I can't search PyPI by author to make sure I have them all.

Very frustrating.

This list is all the PyQt packages I was able to identify.

PyQt Packages
PyQt3D==5.15.5
PyQt3D-Qt5==5.15.2
PyQt5==5.15.6
PyQt5-Qt5==5.15.2
PyQt5-sip==12.10.1
PyQt6==6.3.0
PyQt6-3D==6.3.0
PyQt6-3D-Qt6==6.3.0
PyQt6-Charts==6.3.0
PyQt6-Charts-Qt6==6.3.0
PyQt6-DataVisualization==6.3.0
PyQt6-DataVisualization-Qt6==6.3.0
PyQt6-NetworkAuth==6.3.0
PyQt6-NetworkAuth-Qt6==6.3.0
PyQt6-Qt6==6.3.0
PyQt6-sip==13.3.1
PyQt6-WebEngine==6.3.0
PyQt6-WebEngine-Qt6==6.3.0
PyQtChart==5.15.5
PyQtChart-Qt5==5.15.2
PyQtDataVisualization==5.15.5
PyQtDataVisualization-Qt5==5.15.2
PyQtNetworkAuth==5.15.5
PyQtNetworkAuth-Qt5==5.15.2
PyQtWebEngine==5.15.5
PyQtWebEngine-Qt5==5.15.2

With the above packages installed, the module report makes a lot more sense:

Module Report
Qt3DAnimation       ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
Qt3DCore            ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
Qt3DExtras          ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
Qt3DInput           ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
Qt3DLogic           ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
Qt3DRender          ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtBluetooth*        ['PyQt5', 'PyQt6', 'PySide6']
QtChart*            ['PyQt5']                                 <- great job with the name, Riverbank
QtCharts            ['PyQt6', 'PySide2', 'PySide6']
QtConcurrent*       ['PySide2', 'PySide6']
QtCore              ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtDBus              ['PyQt5', 'PyQt6', 'PySide6']
QtDataVisualization ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtDesigner          ['PyQt5', 'PyQt6', 'PySide6']
QtGui               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtHelp              ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtLocation          ['PyQt5', 'PySide2']
QtMultimedia        ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtMultimediaWidgets ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtNetwork           ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtNetworkAuth       ['PyQt5', 'PyQt6', 'PySide6']
QtNfc*              ['PyQt5', 'PyQt6', 'PySide6']
QtOpenGL            ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtOpenGLFunctions*  ['PySide2']
QtOpenGLWidgets     ['PyQt6', 'PySide6']
QtPositioning       ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtPrintSupport      ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQml               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQuick             ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtQuick3D*          ['PyQt5', 'PyQt6', 'PySide6']
QtQuickControls2*   ['PySide2', 'PySide6']
QtQuickWidgets      ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtRemoteObjects     ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtScript*           ['PySide2']
QtScriptTools*      ['PySide2']
QtScxml*            ['PySide2', 'PySide6']
QtSensors           ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSerialPort        ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSql               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtStateMachine*     ['PySide6']
QtSvg               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtSvgWidgets*       ['PyQt6', 'PySide6']
QtTest              ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtTextToSpeech      ['PyQt5', 'PySide2']
QtUiTools*          ['PySide2', 'PySide6']
QtWebChannel        ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWebEngine         ['PyQt5', 'PySide2']
QtWebEngineCore     ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWebEngineQuick    ['PyQt6', 'PySide6']
QtWebEngineWidgets  ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWebSockets        ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtWidgets           ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtX11Extras*        ['PyQt5', 'PySide2']
QtXml               ['PyQt5', 'PyQt6', 'PySide2', 'PySide6']
QtXmlPatterns       ['PyQt5', 'PySide2']

@CAM-Gerlach
Copy link
Member

I can't search PyPI by author to make sure I have them all.

Searching for "riverbank" shows all of them with a handful of false positives, and viewing Phil Thompson's profile displays what should be all of them more precisely.

@dalthviz
Copy link
Member

dalthviz commented May 9, 2022

Thanks for taking the time to review all the missing imports @DaelonSuzuka and @CAM-Gerlach for giving feedback ! Just as a side note, probably for missing imports of modules that are packaged independently we should do something similar to what is done for QtCharts:

qtpy/qtpy/QtCharts.py

Lines 12 to 29 in 3c6bd90

if PYQT5:
try:
from PyQt5.QtChart import *
from PyQt5 import QtChart as QtCharts
except ImportError as error:
raise PythonQtError(
'The QtChart module was not found. '
'It needs to be installed separately for PyQt5.'
) from error
elif PYQT6:
try:
from PyQt6.QtCharts import *
from PyQt6 import QtCharts
except ImportError as error:
raise PythonQtError(
'The QtCharts module was not found. '
'It needs to be installed separately for PyQt6.'
) from error

@CAM-Gerlach
Copy link
Member

Indeed, and it would be helpful to include the package name to install in such messages, given @DaelonSuzuka 's difficulties finding it

@DaelonSuzuka
Copy link
Contributor Author

DaelonSuzuka commented May 11, 2022

Searching for "riverbank" shows all of them with a handful of false positives, and viewing Phil Thompson's profile displays what should be all of them more precisely.

Indeed, and it would be helpful to include the package name to install in such messages, given @DaelonSuzuka 's difficulties finding it

I think I was expecting to be able to navigate directly from the PyQt5/6 package to a page of that account's uploads or something, and I guess I gave up too soon. Thanks for the double check (again).

Just as a side note, probably for missing imports of modules that are packaged independently we should do something similar to what is done for QtCharts:

@dalthviz I was planning on raising a PythonQtError with a relevant message for every case I can reasonably identify.

  • Win/Mac/X11 extras on the wrong OS
  • module removed in Qt6
  • module does not exist in Qt5
  • module not implemented in currently selected Qt API
  • module is from a different package (including the actual package name)

This has turned out to be a bigger job than I expected, and it's a waste of time to half-ass it, so thanks to both of you for your patience. Hopefully I'll have more time this week and I can get this knocked out,

@CAM-Gerlach
Copy link
Member

I think I was expecting to be able to navigate directly from the PyQt5/6 package to a page of that account's uploads or something, and I guess I gave up too soon.

Oh, did just clicking on the maintainer's account name on the left of the package page not work for you?

This has turned out to be a bigger job than I expected, and it's a waste of time to half-ass it, so thanks to both of you for your patience. Hopefully I'll have more time this week and I can get this knocked out,

Thanks for all your help and hard work! Though, don't forget (as I often do...)—"the perfect is the enemy of the great," as they say...

@DaelonSuzuka
Copy link
Contributor Author

DaelonSuzuka commented May 15, 2022

Wow, that's a lot of failing test output. I think it's all because of the PythonQtError I'm throwing when an invalid import is attempted. It looks like I can use pytest.raises to expect a certain error.

I have to admit I don't have that much experience writing tests, but the structure of the test suite kind of looks like a code smell to me. It feels like it's testing the implementation more than the interface, I don't really have a proposal except for my gut reaction of generating the tests from the Qt libraries(probably not the right thing to do), but I wanted to mention it anyways.

Regardless, I'm wiped for tonight, so I'll start on these tomorrow. I think one more evening will finish off this PR.

Though, don't forget (as I often do...)—"the perfect is the enemy of the great," as they say...

I have definitely fallen prey to that impulse, but in this case I'm just slow because I was sick last week and I'm catching up on all the work I slept through.

@DaelonSuzuka
Copy link
Contributor Author

DaelonSuzuka commented May 15, 2022

Oh, I almost forgot to mention, here and here are all of the new Exception messages.

I would definitely appreciate a proofread, and if you see a better way to phrase any of them I'll be happy to change it.

Edit: I forgot the package names for the external PyQt libraries. Definitely open to suggestions for how to phrase those.

@CAM-Gerlach CAM-Gerlach changed the title PR: Add missing PySide6 import PR: Add missing PySide6 imports May 15, 2022
Copy link
Member

@CAM-Gerlach CAM-Gerlach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, @DaelonSuzuka , that's indeed a lot of work—thanks! Its a sufficiently big extension of QtPy (with many new modules, new errors, and other changes) that I think it makes sense to ship it in 1.2, assuming @dalthviz agrees.

I highly recommend applying my GitHub suggestions first, which are generally straightforward and uncontroversial, before addressing the higher level comments and feedback, assuming you don't disagree with them; otherwise, they are likely to get outdated/lost.

Some high-level feedback and suggestions:

  • Right now, since QtPy is still using the implicit ("flat") package layout rather than the now-recommended explicit src layout, this means that module_report is a top-level import package equal to qtpy itself and will be included in the distribution package and installed in the user's environment as a separate import package, which is of course not what we want. We could exclude it with some futzing in the setuptools configuration, but IMO its still confusing to have two top-level import packages, one of which is just a development-focused helper tool.

    Therefore, after consultation with you and @dalthviz , I suggest we do one of the following (there is a third option, making it part of the qtpy package itself, but I don't think that is more compelling than either of the other two for now):

    • Stick module_report inside a tools directory (which wouldn't be importable and is ignored by default in the new Setuptools 61 auto-discovery) and run it from there when needed (simplest solution to the immediate problem)
    • Make module_report its own separate repo/distribution package (Better usability for us and others without bundling it in QtPy itself, and if you want to help maintain it, you could much more easily be added as a Collaborator on that repo, but is more initial work, more overhead and less directly coupled to QtPy's own development)

    Making it a separate repo, installable (at least) from GitHub, is the "nicest" solution, but also the most work, up front and (to some extent) over time, so whether this is viable depends on whether you want to help maintain it, and also on if it more generally useful to application developers rather than just development of QtPy itself. So IMO, just keeping it in a tools directory is probably the best approach for now unless and until those other conditions are a compelling enough reason to do otherwise (which we could always do in the future).

  • If we're going to check the output files into the repo, we should run module_report in our CIs to catch any changes and either update the files or take other action accordingly. However, given the results can vary between OSes (e.g. DBus on Windows) and distributions (pip/conda), it might be better to just not check them in directly, or if so make the checked-in files specifically for comparison to a per-platform CI check that determines if the output matches the expected (but that latter part would be optional for this PR). This could also help automate and decouple the testing from the details of the implementation, which you brought up some valid concerns about—but again, that could go in a future PR.

  • Instead of making all the exception messages base PythonQtErrors (such that there's no way for tools to programmatically determine whether the module was not installed, not supported on that binding, or some other error occurred) and writing manual messages for each one (which is way more work to add, check and change, and easier to make a typo on), we should subclass it with specific errors of each type, that take a name and a binding argument (with a msg override) and construct the message from there. This also allows us to have it inherit the built-in ModuleNotFoundError so it behaves as expected.

    Therefore, I suggest adding the following to the __init__.py under the existing errors and using them accordingly:

     class QtModuleNotFoundError(ModuleNotFoundError, PythonQtError):
        """Raised when a Python Qt binding submodule is not installed/supported."""
        _msg = 'The {name} module was not found for {binding}.'
        _msg_extra = ''
    
        def __init__(self, *, name, binding=None, msg=None, **msg_kwargs):
            self.binding = binding
            msg = msg or f'{self._msg} {self._msg_extra}'.strip()
            msg = msg.format(name=name, binding=binding, **msg_kwargs)
            super().__init__(msg, name=name)
    
    class QtBindingMissingModuleError(QtModuleNotFoundError):
        """Raised when a module is not supported by a given binding."""
        _msg_extra = 'It is not currently implemented in {binding}.'
    
    class QtModuleNotInstalledError(QtModuleNotFoundError):
        """Raise when a module is supported by the binding, but not installed."""
        _msg_extra = 'It must be installed separately'
    
        def __init__(self, *, missing_package=None, **superclass_kwargs):
            self.missing_package = missing_package
            if missing_package is not None:
                 self._msg_extra += ' as {missing_package}.'
            super().__init__(missing_package=missing_package, **superclass_kwargs)

Also, a few smaller but common issues (which I made suggestions to fix, except where noted):

  • Let's please not use * imports in module_report, as they are considered a very bad practice and make the code hard to understand, introspect and lint, among other reasons. It is true that QtPy uses them internally, but for a very specific purpose—ensuring it picks up the full namespace of the packages it wraps without hardcoding—that isn't the case here. Also, we should use standard, conventional PEP 8 import ordering.
  • Module-level constants are ALL_CAPS per PEP 8 and standard convention
  • Don't forget to add terminating EOLs to the files
  • Per PEP 257, docstrings must end with periods and one-line docstrings should be on one line (as they are currently), not with '''" above and below (I didn't make review comments for this to avoid noise)

Thanks, and looking forward to seeing this in final form!

module_report/Makefile Outdated Show resolved Hide resolved
module_report/README.md Outdated Show resolved Hide resolved
module_report/README.md Outdated Show resolved Hide resolved
module_report/__main__.py Outdated Show resolved Hide resolved
module_report/__main__.py Outdated Show resolved Hide resolved
module_report/check_modules.py Outdated Show resolved Hide resolved
module_report/check_modules.py Outdated Show resolved Hide resolved
module_report/check_modules.py Outdated Show resolved Hide resolved
module_report/check_modules.py Outdated Show resolved Hide resolved
module_report/check_modules.py Outdated Show resolved Hide resolved
@DaelonSuzuka
Copy link
Contributor Author

The other approach that would avoid this would be to add and make the module_report changes in separate commits and then just drop those all at once.

I made sure module report commits and regular commits never touched because I was expecting to "simply" rebase and drop them. I just don't do these operations often enough to be deeply familiar with them.

In any case, the simplest solution is to just do a squash-merge, but that squashes all your hard work here on over a hundred files and thousands of lines down to a single commit, which is not ideal in a number of ways, so I hope we can avoid that if at all possible.

I've seen people advocate for what I guess you could call incremental squashing, where a number of similar commits ("added newlines after imports", "added more newlines after imports") get combined, but it's quite likely I'd make a mess doing that, too.

Let me give it a shot, standby...

Godspeed.

o7

@CAM-Gerlach CAM-Gerlach marked this pull request as ready for review June 10, 2022 23:07
@CAM-Gerlach
Copy link
Member

CAM-Gerlach commented Jun 10, 2022

I just did git rebase -i master and then drop-ed the commits that touched module_report and missing_tests.txt, which you kept nicely separated, and everything worked perfectly as you intended it to—aside from the fact that I missed dropping one commit and then had to go back and fix it manually, heh. My guess is that either you tried a different command (fixup, squash, etc) or (like I did) missed one or more commits. Rewriting git history is a dark art, and something I'm only comfortable with because I do it obsessively (probably too much) on my personal feature branches, both as I work and before merging.

I've seen people advocate for what I guess you could call incremental squashing, where a number of similar commits ("added newlines after imports", "added more newlines after imports") get combined, but it's quite likely I'd make a mess doing that, too.

I usually do this, either at the end or as I work, but I generally avoid it for contributor PRs since it requires rewriting history on their own branch, and ends up being a lot more complexity than either keeping the commits or auto-squashing. In this case, I was going to do do some commit consolidation to make the rebase easier, but it turned out it wasn't necessary since you were careful to keep your commit separate, well-titled and organized, and there were only a few places I might have squashed things, so I just left things as they were.

@CAM-Gerlach
Copy link
Member

In any case, since we're all LGTM and it was pending my review, I'm going to go ahead and merge. Thanks again, everyone, and excited to see your future work @DaelonSuzuka !

@CAM-Gerlach CAM-Gerlach merged commit 5a00230 into spyder-ide:master Jun 10, 2022
@DaelonSuzuka
Copy link
Contributor Author

DaelonSuzuka commented Jun 10, 2022

I just did git rebase -i master and then drop-ed the commits that touched module_report and missing_tests.txt, which you kept nicely separated, and everything worked perfectly as you intended it to—aside from the fact that I missed dropping one and then had to go back and fix it manually. My guess is that either you tried a different command (fixup, squash, etc) or (like I did) missed one or more commits.

I used git rebase -i HEAD~55 and then drop-ed the commits that touched module_report and missing_tests.txt. It's worked before using HEAD~XX as the argument, so I have no idea what was different this time. I'm also fairly confident I dropped all the relevant ones, too.

Thank you for cleaning up after me(again).

In any case, since we're all LGTM and it was pending my review, I'm going to go ahead and merge. Thanks again, everyone, and excited to see your future work @DaelonSuzuka !

I can genuinely say it's been a pleasure working with you gentlemen. I'll try to be back soon to discuss more potential improvements.

@CAM-Gerlach
Copy link
Member

CAM-Gerlach commented Jun 10, 2022

I used git rebase -i HEAD~55 and then drop-ed the commits that touched module_report and missing_tests.txt. It's worked before using HEAD~XX as the argument, so I have no idea what was different this time. I'm also fairly confident I dropped all the relevant ones, too.

Hmm, that all sounds right, although at least for me when I checked out your branch prior to my rebase, HEAD~55 pointed to a different commit than the base of the branch (it was several commits further back), but that could vary depending on when/whether you rebased on master up to that point and what other commits were there up to that point. Even still, it should still work since AFAIK, if all the commits prior to your branch were just picked and there were no changes before them, it doesn't actually modify them. I typically either do git rebase -i master, assuming my feature branch is up to date with my local master (or whatever branch I based it on), or else just copy and paste the hash of the first commit on the branch from git log, i.e. git rebase -i $THE_COMMIT~. But again, this shouldn't make a difference, so we'll probably never know what was the issue here.

I can genuinely say it's been a pleasure working with you gentlemen. I'll try to be back soon to discuss more potential improvements.

Fantastic, same to you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PySide6.QtSvgWidgets not exposed
4 participants